package com.csw.android.androidtest.net.socket.udp;

import com.csw.android.androidtest.net.socket.base.BaseSocket;
import com.csw.android.androidtest.net.socket.base.SocketListener;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;

/**
 * UdpSocket实现,实际上Udp是无连接的，直接发包，过程中可能丢包，这是无法避免的，服务端与客户端没差别，
 * 服务端主要做的是绑定一个稳定的端口用于通信，该端口需要告知客户端，客户端再通过给该端口发信息来实现沟通,
 * 曾经遇到过华为某些机子拥有双网卡，当时试图通过wifi进行socket通信，但因底层自动选择网卡，导致有概率通过移动
 * 数据进行socket连接，最终处于不同网络无法通信，所以如有需要，可以指定ip地址
 */
public class UDPSocket extends BaseSocket {
    private DatagramSocket socket;

    /**
     * 初始化
     *
     * @param ip             本socket所用网卡ip，如是本地socket通信，可以直接使用127.0.0.1
     * @param port           端口，如不能确定该端口一定处于空闲状态，则指定为0，自动选择一个空闲端口，若指定端口被占用
     *                       会抛出异常
     * @param socketListener socket事件监听
     */
    public UDPSocket(String ip, int port, SocketListener socketListener) {
        super(socketListener);
        if (port < 0) {
            port = 0;
        }
        this.ip = ip;
        this.port = port;
    }

    @Override
    protected void closeResource() {
        super.closeResource();
        if (socket != null) {
            try {
                socket.close();
                socket = null;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    protected void execConnect() throws Exception {
        socket = new DatagramSocket(null);
        socket.setSoTimeout(0);//超时设置为0，等于读取不到时阻塞
        socket.setReuseAddress(true);//可重用地址，当有多个连接都用这个ip和端口时不会出现异常
        InetSocketAddress inetSocketAddress;
        if (ip != null) {
            inetSocketAddress = new InetSocketAddress(ip, port);
        } else {
            inetSocketAddress = new InetSocketAddress(port);
        }
        //为socket绑定地址和端口
        socket.bind(inetSocketAddress);
        ip = socket.getLocalAddress().getHostAddress();
        port = socket.getLocalPort();
        socketListener.onPrepared(ip, port);
    }

    @Override
    protected void execRead() throws Exception {
        UDPMessage udpMessage = null;
        while (getState() == 2) {
            final DatagramSocket s = socket;
            if (s != null) {
                byte[] packetData = new byte[1472];
                DatagramPacket datagramPacket = new DatagramPacket(
                        packetData,
                        0,
                        packetData.length,
                        new InetSocketAddress(destIP, destPort)
                );
                s.receive(datagramPacket);
                if (datagramPacket.getLength() > 0) {
                    byte[] data = new byte[datagramPacket.getLength()];
                    System.arraycopy(packetData, 0, data, 0, data.length);
                    Packet packet = Packet.parsePacket(data);
                    if (udpMessage == null) {
                        udpMessage = new UDPMessage(packet.getMessageId());
                    }
                    if (packet.getMessageId() != udpMessage.getId()) {
                        udpMessage = new UDPMessage(packet.getMessageId());
                    }
                    udpMessage.addPacket(packet);
                    if (packet.getIsLast() == 1) {
                        if (udpMessage.checkDataEnable()) {
                            socketListener.onReceiveMessage(udpMessage.getData());
                        }
                        udpMessage = null;
                    }
                }
            }
        }
    }

    private int sendMessageID = 0;

    @Override
    protected void execWrite(byte[] data) throws Exception {
        final DatagramSocket s = socket;
        if (s != null && data.length > 0) {
            //将数据分包，发送到目标端口
            final int messageId = sendMessageID++;
            if (sendMessageID > 255) {
                sendMessageID = 0;
            }
            UDPMessage udpMessage = new UDPMessage((byte) messageId);
            udpMessage.setData(data);
            byte[] packetData;
            for (Packet packet : udpMessage.getPackets()) {
                packetData = packet.getDataWithHeader();
                DatagramPacket datagramPacket = new DatagramPacket(
                        packetData,
                        0,
                        packetData.length,
                        new InetSocketAddress(destIP, destPort)
                );
                s.send(datagramPacket);
            }
        }
    }

}
